/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <am_mcu_apollo.h>
#include <am_bsp.h>
#include <tusb.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_module.h>
#include <k_task.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

#define ENABLE_SYSPLL_FOR_HS                                                                       \
    ((BOARD_DEVICE_RHPORT_SPEED == OPT_MODE_HIGH_SPEED) && defined(AM_PART_APOLLO510))

const char welcome_text[] = "\r\nTinyUSB CDC MSC eMMC device example\r\n";

//*********************************************************************
//
// USB CDC
//
//*********************************************************************
void cdc_task(void)
{
#ifdef AM_CDC_USE_APP_BUF
    tud_app_buf_state_t rx_state = tud_cdc_rx_buf_state_get();
    tud_app_buf_state_t tx_state = tud_cdc_tx_buf_state_get();

    // If TX buffer status is completed/idle/bus-reset
    if (tx_state != TUD_APP_BUF_STATE_BUSY) {
        // Clear buffer valid count for current buffer slot
        gBufValidCnt[gTxPtr] = 0;

        // If there is new buffer to transmit
        uint8_t nextTxPtr = (gTxPtr + 1) % APP_BUF_COUNT;
        if (gBufValidCnt[nextTxPtr] != 0) {
            // Transmit buffer and update TX Buffer pointer to the buffer slot
            // being transmitted
            tud_cdc_tx_buf_assign_send(gBuf[nextTxPtr], gBufValidCnt[nextTxPtr]);
            gTxPtr = nextTxPtr;
        }
    }

    // If current RX buffer has done receiving data
    if (rx_state == TUD_APP_BUF_STATE_RX_READY) {
        uint8_t nextRxPtr = gRxPtr;
        uint32_t recv_cnt = tud_cdc_rx_buf_recv_count_get();

        // If received count is not zero
        if (recv_cnt > 0) {
            nextRxPtr = (gRxPtr + 1) % APP_BUF_COUNT;
            // If next buffer is empty
            if (gBufValidCnt[nextRxPtr] == 0) {
                // Record buffer valid count for current buffer slot
                gBufValidCnt[gRxPtr] = recv_cnt;

                // Increment pointer to the empty slot to start receiving
                gRxPtr = nextRxPtr;

                // Start receiving from USB Host
                tud_cdc_rx_buf_assign(gBuf[gRxPtr], sizeof(gBuf[gRxPtr]));
            }
        }
        // Nothing is received into the buffer. Receive back into same buffer
        else {
            // Start receiving from USB Host
            tud_cdc_rx_buf_assign(gBuf[gRxPtr], sizeof(gBuf[gRxPtr]));
        }
    }
    // If buffer reset occurred
    else if (rx_state == TUD_APP_BUF_STATE_BUF_RESET) {
        // re-assign current buffer to receive, discarding data received before
        // buffer reset.
        tud_cdc_rx_buf_assign(gBuf[gRxPtr], sizeof(gBuf[gRxPtr]));
    }
#else   // AM_CDC_USE_APP_BUF

    // connected() check for DTR bit
    // Most but not all terminal client set this when making connection
    // if ( tud_cdc_connected() )
    {
        // connected and there are data available
        uint32_t rx_count = tud_cdc_available();
        uint32_t tx_avail = tud_cdc_write_available();
        if ((rx_count != 0) && (tx_avail >= rx_count)) {
            uint8_t buf_rx[CFG_TUD_CDC_RX_BUFSIZE];
#if defined(CFG_TUD_SPEED_HS2FS_CONVER)

            uint32_t but_rx_active_len = CFG_TUD_CDC_RX_BUFSIZE;

            if (tusb_request_speed_get() == TUSB_SPEED_FULL) {
                but_rx_active_len = 64;
            }
            // read and echo back
            uint32_t count = tud_cdc_read(buf_rx, but_rx_active_len);
#else
            uint32_t count = tud_cdc_read(buf_rx, sizeof(buf_rx));
#endif
            tud_cdc_write(buf_rx, count);
            tud_cdc_write_flush();
        }
    }
#endif   // AM_CDC_USE_APP_BUF
}

//*********************************************************************
//
// Invoked when device is mounted
//
//*********************************************************************
void tud_mount_cb(void)
{
    printf("tud MOUNTED\n");
}

//*********************************************************************
//
// Invoked when device is unmounted
//
//*********************************************************************
void tud_umount_cb(void)
{
#ifdef CFG_TUD_SPEED_HS2FS_CONVER
    tusb_speed_t speed = tusb_request_speed_get();

    if (speed == TUSB_SPEED_HIGH) {
        printf("Initialize the USB to Full Speed\n");
        tusb_request_speed_set(TUSB_SPEED_FULL);
    } else {
        printf("Initialize the USB to High Speed\n");
        tusb_request_speed_set(TUSB_SPEED_HIGH);
    }

    tud_connect();
    printf("Connect Done\n");

#else    // CFG_TUD_SPEED_HS2FS_CONVER
    printf("tud UMOUNTED\n");
#endif   // CFG_TUD_SPEED_HS2FS_CONVER
}

//*********************************************************************
//
// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us  to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
//
//*********************************************************************
void tud_suspend_cb(bool remote_wakeup_en)
{
    (void)remote_wakeup_en;
}

//*********************************************************************
//
// Invoked when usb bus is resumed
//
//*********************************************************************
void tud_resume_cb(void)
{
}

//*********************************************************************
//
// Invoked when cdc when line state changed e.g connected/disconnected
//
//*********************************************************************
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
{
    (void)itf;
    (void)rts;

#ifdef AM_CDC_USE_APP_BUF
    if (dtr) {
        tud_app_buf_state_t tx_state = tud_cdc_tx_buf_state_get();
        if (tx_state != TUD_APP_BUF_STATE_BUSY) {
            if (gBufValidCnt[gTxPtr] == 0) {
                sprintf((char *)gBuf[gTxPtr], "%s", welcome_text);
                tud_cdc_tx_buf_assign_send(gBuf[gTxPtr], sizeof(welcome_text));
            }
        }
    }
#else    // AM_CDC_USE_APP_BUF
    // connected
    if (dtr) {
        // print initial message when connected
        tud_cdc_write_str(welcome_text);
        tud_cdc_write_flush();
    }
#endif   // AM_CDC_USE_APP_BUF
}

//*********************************************************************
//
// Invoked when CDC interface received data from host
//
//*********************************************************************
void tud_cdc_rx_cb(uint8_t itf)
{
    (void)itf;
}

static int usb_device_task(uintptr_t arg)
{
    // init device stack on configured roothub port
    // This should be called after scheduler/kernel is started.
    // Otherwise it could cause kernel issue since USB IRQ handler does use RTOS queue API.
    tusb_rhport_init_t dev_init = {
        .role = TUSB_ROLE_DEVICE,
        .speed = TUSB_SPEED_HIGH,
    };
    tusb_init(BOARD_TUD_RHPORT, &dev_init);

    while (1) {
        //
        // tinyusb device task
        //
        tud_task();

        //
        // USB CDC Task
        //
        cdc_task();
    }
    return 0;
}

static int usb_init(void)
{
    am_bsp_low_power_init();

#if ENABLE_SYSPLL_FOR_HS
    am_hal_clkmgr_clock_config(AM_HAL_CLKMGR_CLK_ID_SYSPLL, AM_HAL_USB_PHY_CLK_FREQ_HZ, NULL);
#endif   // ENABLE_SYSPLL_FOR_HS

    task_spawn("tud", 5, 0, 0x1000, usb_device_task, 0);

    return 0;
}

MODULE_INIT(postboard, usb_init);
